﻿/****************************************************************************************************
	Copyright (C) 2010 RapidWebDev Organization (http://rapidwebdev.org)
	Author: Eunge, Legal Name: Jian Liu, Email: eunge.liu@RapidWebDev.org

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Transactions;
using System.Web;
using System.Web.Profile;
using System.Web.Security;
using BaoJianSoft.Common;
using BaoJianSoft.Common.Caching;
using BaoJianSoft.Common.Data;
using BaoJianSoft.Common.Validation;
using BaoJianSoft.ExtensionModel;
using BaoJianSoft.Platform.Initialization;
using BaoJianSoft.Platform.Properties;

namespace BaoJianSoft.Platform.Linq
{
	/// <summary>
	/// The implementation organization API which intents to cache all organization types and organizations and interact with database by linq-2-sql OrganizationDbProvider.
	/// </summary>
	public class OrganizationApi : CachableApi, IOrganizationApi
	{
		private IAuthenticationContext authenticationContext;
		private IPlatformConfiguration platformConfiguration;
		private IHierarchyApi hierarchyApi;
		private OrganizationDbProvider dbProvider;

		/// <summary>
		/// Construct OrganizationApi instance.
		/// </summary>
		/// <param name="authenticationContext"></param>
		/// <param name="platformConfiguration"></param>
		/// <param name="hierarchyApi"></param>
		public OrganizationApi(IAuthenticationContext authenticationContext, IPlatformConfiguration platformConfiguration, IHierarchyApi hierarchyApi)
		{
			this.authenticationContext = authenticationContext;
			this.platformConfiguration = platformConfiguration;
			this.hierarchyApi = hierarchyApi;

			this.dbProvider = new OrganizationDbProvider(authenticationContext, platformConfiguration, hierarchyApi);
		}

		#region Organization Type

		/// <summary>
		/// Save organization type object.
		/// </summary>
		/// <param name="organizationTypeObject"></param>
		/// <exception cref="ValidationException">etc organization type name does exist.</exception>
		public void Save(OrganizationTypeObject organizationTypeObject)
		{
			Kit.NotNull(organizationTypeObject, "organizationTypeObject");
			Kit.NotNull(organizationTypeObject.Name, "organizationTypeObject.Name");
			if (!this.platformConfiguration.Domains.Select(d => d.Value).Contains(organizationTypeObject.Domain))
				throw new ArgumentException(string.Format(Resources.InvalidOrganizationTypeDomain, organizationTypeObject.Domain), "organizationTypeObject.Domain");

			// update database
			this.dbProvider.Save(organizationTypeObject);

			// remove cache
			base.RemoveCache(this.CacheKey4AllOrganizationTypes());
		}

		/// <summary>
		/// Get organization type business object by id.
		/// </summary>
		/// <param name="organizationTypeId">organization type id</param>
		/// <returns></returns>
		public OrganizationTypeObject GetOrganizationType(Guid organizationTypeId)
		{
			var returnValue = this.FindOrganizationTypesInternal().FirstOrDefault(orgType => orgType.OrganizationTypeId == organizationTypeId);
			if (returnValue == null) return null;
			return returnValue.Clone();
		}

		/// <summary>
		/// Get organization type by name.
		/// </summary>
		/// <param name="organizationTypeName">organization type name</param>
		/// <returns></returns>
		public OrganizationTypeObject GetOrganizationType(string organizationTypeName)
		{
			var returnValue = this.FindOrganizationTypesInternal().FirstOrDefault(orgType => orgType.Name == organizationTypeName);
			if (returnValue == null) return null;
			return returnValue.Clone();
		}

		/// <summary>
		/// Find all organization types for current application.
		/// </summary>
		/// <returns></returns>
		public IEnumerable<OrganizationTypeObject> FindOrganizationTypes()
		{
			return this.FindOrganizationTypesInternal();
		}

		/// <summary>
		/// Find all organization types in specified domains in current application.
		/// </summary>
		/// <param name="domains"></param>
		/// <returns></returns>
		public IEnumerable<OrganizationTypeObject> FindOrganizationTypes(IEnumerable<string> domains)
		{
			Kit.NotNull(domains, "domains");
			return this.FindOrganizationTypesInternal().Where(orgType => domains.Contains(orgType.Domain)).Select(orgType => orgType.Clone());
		}

		private IEnumerable<OrganizationTypeObject> FindOrganizationTypesInternal()
		{
			try
			{
				IEnumerable<OrganizationTypeObject> organizationTypeObjects = base.GetCacheObject<IEnumerable<OrganizationTypeObject>>(this.CacheKey4AllOrganizationTypes());
				if (organizationTypeObjects == null)
				{
					organizationTypeObjects = this.dbProvider.FindAllOrganizationTypes();
					base.AddCache(this.CacheKey4AllOrganizationTypes(), new List<OrganizationTypeObject>(organizationTypeObjects));
				}

				return organizationTypeObjects;
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				throw;
			}
		}

		#endregion

		#region Organization

		/// <summary>
		/// Save organization business object. 
		/// If organizationObject.Id equals Guid.Empty, it means to save a new organization. 
		/// Otherwise it's updating an existed organization.
		/// </summary>
		/// <param name="organizationObject"></param>
		public void Save(OrganizationObject organizationObject)
		{
			Kit.NotNull(organizationObject, "organizationObject");
			Kit.NotNull(organizationObject.OrganizationName, "organizationObject.Name");
			if (organizationObject.Status == OrganizationStatus.None)
				throw new ArgumentException(Resources.OrganizationStatusNotSpecified, "organizationObject.Status");

			try
			{
				using (RwdTransactionScope transactionScope = new RwdTransactionScope())
				{
					bool isNewOrganization = organizationObject.OrganizationId == Guid.Empty;
					this.dbProvider.Save(organizationObject);

					// try to update cache for "all organizations"
					IEnumerable<OrganizationObject> cachedOrganizations = base.GetCacheObject<IEnumerable<OrganizationObject>>(this.CacheKey4AllOrganizations());
					if (cachedOrganizations != null)
					{
						List<OrganizationObject> newCachingOrganizations = new List<OrganizationObject>(cachedOrganizations);
						if (isNewOrganization)
							newCachingOrganizations.Add(organizationObject);
						else
						{
							OrganizationObject cachedOrganizationObject = cachedOrganizations.FirstOrDefault(org => org.OrganizationId == organizationObject.OrganizationId);
							newCachingOrganizations.Remove(cachedOrganizationObject);
							newCachingOrganizations.Add(organizationObject);
							UpdateChildOrganizationStatus(organizationObject.OrganizationId, newCachingOrganizations, organizationObject.Status);
						}

						base.AddCache(this.CacheKey4AllOrganizations(), newCachingOrganizations);
					}

					base.AddCache(organizationObject.OrganizationId, organizationObject);
					transactionScope.Complete();
				}
			}
			catch (ValidationException)
			{
				throw;
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				throw;
			}
		}

		/// <summary>
		/// Find organization elements by enumerable organization ids.
		/// </summary>
		/// <param name="organizationIdList">enumerabe organization type ids, null or empty value indicates to query all organizations</param>
		/// <returns></returns>
		public IDictionary<Guid, OrganizationObject> BulkGetOrganizations(IEnumerable<Guid> organizationIdList)
		{
			return this.FindAllOrganizations().Where(org => organizationIdList.Contains(org.OrganizationId)).ToDictionary(org => org.OrganizationId, org => org.Clone());
		}

		/// <summary>
		/// Get organization instance by id.
		/// </summary>
		/// <param name="organizationId">organization id</param>
		/// <returns></returns>
		public OrganizationObject GetOrganization(Guid organizationId)
		{
			OrganizationObject organizationObject = base.GetCacheObject<OrganizationObject>(organizationId);
			if (organizationObject != null) return organizationObject;

			organizationObject = this.FindAllOrganizations().FirstOrDefault(org => org.OrganizationId == organizationId);
			base.AddCache(organizationObject.OrganizationId, organizationObject);

			return organizationObject.Clone();
		}

		/// <summary>
		/// Get organization instance by name.
		/// </summary>
		/// <param name="organizationName">organization name</param>
		/// <returns></returns>
		public OrganizationObject GetOrganizationByName(string organizationName)
		{
			OrganizationObject returnValue = this.FindAllOrganizations().FirstOrDefault(org => org.OrganizationName == organizationName);
			if (returnValue == null) return null;
			return returnValue.Clone();
		}

		/// <summary>
		/// Get organization instance by code.
		/// </summary>
		/// <param name="organizationCode">organization code</param>
		/// <returns></returns>
		public OrganizationObject GetOrganizationByCode(string organizationCode)
		{
			OrganizationObject returnValue = this.FindAllOrganizations().FirstOrDefault(org => org.OrganizationCode == organizationCode);
			if (returnValue == null) return null;
			return returnValue.Clone();
		}

		/// <summary>
		/// Find all enabled child organization ids includes not immediately.
		/// </summary>
		/// <param name="parentOrganizationId"></param>
		/// <returns></returns>
		public IEnumerable<Guid> FindAllEnabledChildOrganizationIds(Guid parentOrganizationId)
		{
			IEnumerable<OrganizationObject> allOrganizations = this.FindAllOrganizations();
			List<Guid> results = new List<Guid>();
			FindChildOrganizationIds(parentOrganizationId, allOrganizations, results);
			return results.Distinct();
		}

		private IEnumerable<OrganizationObject> FindAllOrganizations()
		{
			try
			{
				IEnumerable<OrganizationObject> cachedOrganizations = base.GetCacheObject<IEnumerable<OrganizationObject>>(this.CacheKey4AllOrganizations());
				if (cachedOrganizations != null) return cachedOrganizations;

				cachedOrganizations = this.dbProvider.FindAllOrganizations();
				foreach (OrganizationObject organizationObject in cachedOrganizations)
					base.AddCache(organizationObject.OrganizationId, organizationObject);

				base.AddCache(this.CacheKey4AllOrganizations(), new List<OrganizationObject>(cachedOrganizations));

				return cachedOrganizations;
			}
			catch (ArgumentException)
			{
				throw;
			}
			catch (ValidationException)
			{
				throw;
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				throw;
			}
		}

		private void UpdateChildOrganizationStatus(Guid parentOrgId, IEnumerable<OrganizationObject> allOrganizationObjects, OrganizationStatus status)
		{
			if (status == OrganizationStatus.Enabled) return;
			IEnumerable<OrganizationObject> childOrgs = allOrganizationObjects.Where(org => org.ParentOrganizationId == parentOrgId);
			foreach (OrganizationObject childOrg in childOrgs)
			{
				childOrg.Status = status;
				base.AddCache(childOrg.OrganizationId, childOrg);
				UpdateChildOrganizationStatus(childOrg.OrganizationId, allOrganizationObjects, status);
			}
		}

		private static void FindChildOrganizationIds(Guid parentOrganizationId, IEnumerable<OrganizationObject> allOrganizationObjects, List<Guid> results)
		{
			IEnumerable<OrganizationObject> childOrganizations = allOrganizationObjects.Where(org => org.ParentOrganizationId == parentOrganizationId);
			foreach (OrganizationObject childOrganization in childOrganizations)
			{
				results.Add(childOrganization.OrganizationId);
				FindChildOrganizationIds(childOrganization.OrganizationId, allOrganizationObjects, results);
			}
		}

		private string CacheKey4AllOrganizationTypes()
		{
			return "CacheKey4AllOrganizationTypes@" + this.authenticationContext.ApplicationId.ToString();
		}

		private string CacheKey4AllOrganizations()
		{
			return "CacheKey4AllOrganizations@" + this.authenticationContext.ApplicationId.ToString();
		}

		#endregion
	}
}
